/**
 * Initial sync runs in several phases - the first 3 are as follows:
 * 1) fetches the last oplog entry (op_start1) on the source;
 * 2) copies all non-local databases from the source; and
 * 3) fetches and applies operations from the source after op_start1.
 *
 * Between phases 1 and 2, this test updates array fields and subdocument fields with both the
 * "update" and "applyOps" commands on the source, then replaces the array/subdoc fields with
 * strings. The secondary will fail to apply the update operation in phase 3 but initial sync
 * completes nevertheless. The absence of the array/subdoc on the source indicates that a later
 * operation has replaced the field, so the target is free to ignore the failed update operation.
 */

import {ReplSetTest} from "jstests/libs/replsettest.js";
import {
    finishAndValidate,
    reInitiateSetWithSecondary
} from "jstests/replsets/libs/initial_sync_update_missing_doc.js";

const replSet = new ReplSetTest({nodes: 1});

replSet.startSet();
replSet.initiate();

const primary = replSet.getPrimary();
const dbName = 'test';
const collectionName = jsTestName();

const db = primary.getDB(dbName);
const coll = db.getCollection(collectionName);

jsTestLog("Insert some documents with array and subdocument fields");

for (let i = 0; i < 8; ++i) {
    assert.commandWorked(coll.insertOne({_id: i, array: [0], doc: {field: 0}}));
}

jsTestLog("Add a secondary");

const secondaryConfig = {
    rsConfig: {votes: 0, priority: 0}
};
const secondary = reInitiateSetWithSecondary(replSet, secondaryConfig);

jsTestLog("Use both 'update' and 'applyOps' to update docs on primary");

coll.updateMany({}, {$set: {scalar: 0}});

// Update the 8 documents in different ways:
//     * use updateOne or applyOps
//     * update the subdocument or array field
//     * also update the scalar field, or don't
assert.commandWorked(coll.updateOne({_id: 1}, {$set: {'doc.field': 1, 'scalar': 1}}));
assert.commandWorked(coll.updateOne({_id: 0}, {$set: {'doc.field': 1}}));

assert.commandWorked(coll.updateOne({_id: 3}, {$set: {'array.0': 1, 'scalar': 1}}));
assert.commandWorked(coll.updateOne({_id: 2}, {$set: {'array.0': 1}}));

assert.commandWorked(primary.adminCommand({
    applyOps: [{
        op: 'u',
        ns: coll.getFullName(),
        o2: {_id: 5},
        o: {$v: 2, diff: {u: {'scalar': 1}, sdoc: {u: {field: 1}}}}
    }]
}));

assert.commandWorked(primary.adminCommand({
    applyOps:
        [{op: 'u', ns: coll.getFullName(), o2: {_id: 4}, o: {$v: 2, diff: {sdoc: {u: {field: 1}}}}}]
}));

assert.commandWorked(primary.adminCommand({
    applyOps: [{
        op: 'u',
        ns: coll.getFullName(),
        o2: {_id: 7},
        o: {$v: 2, diff: {u: {'scalar': 1}, sarray: {a: true, u0: 1}}}
    }]
}));

assert.commandWorked(primary.adminCommand({
    applyOps: [{
        op: 'u',
        ns: coll.getFullName(),
        o2: {_id: 6},
        o: {$v: 2, diff: {sarray: {a: true, u0: 1}}}
    }]
}));

jsTestLog("Set array and subdoc fields to strings on primary");

assert.commandWorked(coll.updateMany({}, {$set: {array: 'string', doc: 'string'}}));

jsTestLog("Allow initial sync to finish");

assert.commandWorked(secondary.getDB('admin').runCommand(
    {configureFailPoint: 'initialSyncHangBeforeCopyingDatabases', mode: 'off'}));

jsTestLog(`Collection on primary: ${tojson(coll.find().toArray())}`);

finishAndValidate(replSet, collectionName, 8);

replSet.stopSet();